package org.smartboot.compare.comparator;

import org.smartboot.compare.ComparatorContext;
import org.smartboot.compare.Option;
import org.smartboot.compare.Path;
import org.smartboot.compare.difference.AttributeCompareDifference;
import org.smartboot.compare.difference.Difference;
import org.smartboot.compare.difference.NullOfOneObject;
import org.smartboot.compare.difference.BaseDifference;
import org.smartboot.compare.utils.ComparatorUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author qinluo
 * @version 1.0.0
 * @date 2020-08-18 15:27
 */
public class AttributesComparator extends AbstractComparator<String> {

    private static final String KEY_SEPARATOR = ";";
    private static final String VALUE_SEPARATOR = ":";

    private static final AttributesComparator INSTANCE = new AttributesComparator();

    public static AttributesComparator getInstance() {
        return INSTANCE;
    }

    @Override
    public Difference compare(String expect, String actual, ComparatorContext<String> context) {
        if (ComparatorUtils.checkBlank(expect, actual) && context.hasOption(Option.LOOSE_MODE)) {
            return Difference.SAME;
        }

        if (expect == null || actual == null) {
            return new NullOfOneObject(context.getPath(), expect, actual);
        }

        // fast compare.
        if (Objects.equals(expect, actual)) {
            return Difference.SAME;
        }


        boolean hasError = false;
        Map<String, String> expectAttributeMap = parseAttribute(expect);
        Map<String, String> compareAttributeMap = parseAttribute(actual);

        AttributeCompareDifference failItem = new AttributeCompareDifference(context.getPath());

        Set<String> keys = expectAttributeMap.keySet();
        Set<String> compareKeys = compareAttributeMap.keySet();

        for (String key : keys) {
            String expectVal = expectAttributeMap.get(key);
            String compareVal = compareAttributeMap.get(key);

            if (compareVal == null) {
                hasError = true;
                failItem.getExpectKeys().add(key);
                continue;
            }

            compareKeys.remove(key);

            if (!Objects.equals(expectVal, compareVal)) {
                hasError = true;
                BaseDifference item = new BaseDifference(context.createPath(Path.NORMAL, key, null));
                item.setExpect(expectVal);
                item.setActual(compareVal);
                failItem.getNotEqualsFailItems().add(item);
            }
        }

        failItem.getCompareKeys().addAll(compareKeys);

        if (hasError || !compareKeys.isEmpty()) {
            return failItem;
        }

        return Difference.SAME;
    }

    private Map<String, String> parseAttribute(String attributes) {
        String[] expectAttributes = attributes.split(KEY_SEPARATOR);

        Map<String, String> attributesMap = new HashMap<>(16);

        for (String expectAttribute : expectAttributes) {
            String[] split = expectAttribute.split(VALUE_SEPARATOR);
            // ignore non-standard attributes.
            if (split.length != 2) {
                continue;
            }

            attributesMap.put(split[0], split[1]);
        }

        return attributesMap;
    }
}
